﻿using System;
using System.Collections.Generic;
using Gdi = System.Drawing;
using Gdi2D = System.Drawing.Drawing2D;
using GdiImaging = System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using iNet.IO;
using iNet.Tasks;

namespace iNet.Media.Imaging
{
	/// <summary>
	/// 影像格式及大小轉換工作。
	/// </summary>
	public class ImageFormatConversionTask : MediaTask
	{
		#region 欄位
		IImageFormat _AlternativeEncodingFormat;
		MediaStorage _Destination;
		IImageFormat _EncodingFormat;
		Gdi2D.InterpolationMode _InterpolationMode = Gdi2D.InterpolationMode.Default;
		bool _IsTempDestinationFile;
		bool _MaintainAspectRatio = true;
		int? _MaxPixelHeight;
		int? _MaxPixelWidth;
		bool _PreserveMetadata = true;
		MediaStorage _Source;
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 ImageFormatConversionTask 執行個體。
		/// </summary>
		public ImageFormatConversionTask()
		{
			this.InitialProgress = 0;
		}
		/// <summary>
		/// 初始化新的 ImageFormatConversionTask 執行個體。
		/// </summary>
		/// <param name="source">來源影像的儲存來源。</param>
		public ImageFormatConversionTask(MediaStorage source)
			: this()
		{
			_Source = source;
		}
		#endregion
		#region AlternativeEncodingFormat
		/// <summary>
		/// 取得或設定當原始格式或 EncodingFormat 皆不支援時所使用的替代編碼格式。
		/// </summary>
		public IImageFormat AlternativeEncodingFormat
		{
			get
			{
				return _AlternativeEncodingFormat;
			}
			set
			{
				this.SetInitializingParameter<IImageFormat>(ref _AlternativeEncodingFormat, value);
			}
		}
		#endregion
		#region ConvertFormat
		TaskState ConvertFormat(TaskVersion version, ImageDecodeTask decodeTask, ImageEncodeTask encodeTask, int? maxWidth, int? maxHeight, bool maintainRatio, bool preserveMetadata, Gdi2D.InterpolationMode interpolationMode)
		{
			//解碼影像
			decodeTask.MediaDecodeRequests = (preserveMetadata ? MediaDecodeRequests.DecodeMetadata : 0);
			decodeTask.ImageDecodeRequests = ImageDecodeRequests.DecodeImage;
			decodeTask.ProgressChanged += (sender, e) =>
			{
				this.ReportProgress(version, e.Progress * 0.5);
			};
			TaskState result = this.ExecuteChildTask(version, decodeTask);
			if (result == TaskState.Executed)
				this.ReportProgress(version, 50);
			else
				return result;

			//確認解碼影像
			IImage sourceImage = decodeTask.DecodedImage;
			if (sourceImage == null)
				return TaskState.Error;

			//變更影像大小
			IBitmapImage sourceBitmap = (sourceImage as IBitmapImage);
			if (sourceBitmap != null)
			{
				if (sourceBitmap.PixelWidth > maxWidth || sourceBitmap.PixelHeight > maxHeight)
					sourceImage = this.ResizeImage(sourceBitmap, maxWidth, maxHeight, maintainRatio, interpolationMode);
			}

			//編碼影像
			if (preserveMetadata)
			{
				encodeTask.MediaEncodeRequests = MediaEncodeRequests.EncodeMetadata;
				encodeTask.EncodingMetadata = decodeTask.DecodedMetadata;
			}
			else
				encodeTask.MediaEncodeRequests = 0;
			encodeTask.ImageEncodeRequests = ImageEncodeRequests.EncodeImage;
			encodeTask.EncodingImage = sourceImage;
			return this.ExecuteChildTask(version, encodeTask);
		}
		#endregion
		#region CopyToDestination
		TaskState CopyToDestination(TaskVersion version, MediaStorage source, MediaStorage destination, string tempFilePath)
		{
			//開啟輸出資料流
			Stream outStream;
			if (tempFilePath == null)
			{
				if (!this.GetMediaStorageStream(version, destination, MediaStorageStreamFlags.Writable | MediaStorageStreamFlags.Seekable, 0, out outStream))
					return TaskState.Error;
			}
			else
			{
				try
				{
					outStream = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write);
				}
				catch
				{
					return TaskState.Error;
				}
			}

			//複製資料
			try
			{
				//開啟輸入資料流
				Stream inStream;
				if (!this.GetMediaStorageStream(version, source, 0, out inStream))
					return TaskState.Error;

				//複製資料
				StreamCopyTask copyTask = new StreamCopyTask(inStream, outStream);
				copyTask.ProgressChanged += (sender, e) =>
				{
					this.ReportProgress(version, e.Progress);
				};
				try
				{
					TaskState result = this.ExecuteChildTask(version, copyTask);
					if (result == TaskState.Executed && tempFilePath == null)
					{
						if (!this.CommitToMediaStorage(version, destination, 0, outStream))
							return TaskState.Error;
					}
					return result;
				}
				finally
				{
					this.ReleaseMediaStorageStream(version, source, inStream);
				}
			}
			finally
			{
				//關閉輸出資料流
				if (tempFilePath == null)
					this.ReleaseMediaStorageStream(version, destination, outStream);
				else
					outStream.Close();
			}
		}
		#endregion
		#region Destination
		/// <summary>
		/// 取得或設定轉換後的影像儲存來源，設定 Null (Visual Basic 為 Nothing) 表示轉換後儲存至暫存檔案。
		/// </summary>
		public MediaStorage Destination
		{
			get
			{
				return _Destination;
			}
			set
			{
				this.SetInitializingParameter<MediaStorage>(ref _Destination, value);
			}
		}
		#endregion
		#region EncodingFormat
		/// <summary>
		/// 取得或設定轉換後的影像格式，Null (Visual Basic 為 Nothing) 表示使用原始格式。
		/// </summary>
		public IImageFormat EncodingFormat
		{
			get
			{
				return _EncodingFormat;
			}
			set
			{
				this.SetInitializingParameter<IImageFormat>(ref _EncodingFormat, value);
			}
		}
		#endregion
		#region Execute
		/// <summary>
		/// 執行工作。
		/// </summary>
		/// <param name="version">工作版本。</param>
		/// <returns>True 表示工作執行成功，False 表示執行失敗。</returns>
		protected override bool Execute(TaskVersion version)
		{
			//取得狀態
			int? maxWidth, maxHeight;
			bool maintainRatio, preserveMetadata;
			IImageFormat outFormat, altOutFormat;
			MediaStorage source, destination;
			Gdi2D.InterpolationMode interpolationMode;
			if (!this.ReadValue<int?>(version, ref _MaxPixelWidth, out maxWidth)
				|| !this.ReadValue<int?>(version, ref _MaxPixelHeight, out maxHeight)
				|| !this.ReadValue<bool>(version, ref _MaintainAspectRatio, out maintainRatio)
				|| !this.ReadValue<bool>(version, ref _PreserveMetadata, out preserveMetadata)
				|| !this.ReadValue<IImageFormat>(version, ref _EncodingFormat, out outFormat)
				|| !this.ReadValue<IImageFormat>(version, ref _AlternativeEncodingFormat, out altOutFormat)
				|| !this.ReadValue<MediaStorage>(version, ref _Source, out source)
				|| !this.ReadValue<MediaStorage>(version, ref _Destination, out destination)
				|| !this.ReadValue<Gdi2D.InterpolationMode>(version, ref _InterpolationMode, out interpolationMode))
			{
				return true;
			}

			//建立解碼工作
			ImageDecodeTask decodeTask = new GenericImageDecodeTask(source);

			//確認是否需要轉換格式
			bool needConversion = false;
			if (maxWidth != null || maxHeight != null)
			{
				decodeTask.MediaDecodeRequests = 0;
				decodeTask.ImageDecodeRequests = ImageDecodeRequests.DecodePixelSize;
				switch (this.ExecuteChildTask(version, decodeTask))
				{
					case TaskState.Executed:
						break;
					case TaskState.Canceled:
						return true;
					default:
						return false;
				}
				if (!decodeTask.DecodedPixelSize.GetValueOrDefault().IsEmpty)
					needConversion = (decodeTask.DecodedPixelSize.Value.Width > maxWidth || decodeTask.DecodedPixelSize.Value.Height > maxHeight);
				decodeTask.Reset();
			}
			if (!needConversion && outFormat != null)
				needConversion = (decodeTask.DecodingFormat != outFormat);
			if (!needConversion && !preserveMetadata)
				needConversion = true;

			//建立編碼工作
			ImageEncodeTask encodeTask = ImageCodecs.CreateEncodeTask((outFormat == null ? decodeTask.DecodingFormat : outFormat));
			if (encodeTask == null)
			{
				if (altOutFormat != null)
					encodeTask = ImageCodecs.CreateEncodeTask(altOutFormat);
				if (encodeTask == null)
					return false;
				needConversion = true;
			}

			//中斷確認
			if (this.IsCanceling)
				return true;

			//建立暫存檔案
			string tempFilePath;
			if (destination != null)
				tempFilePath = null;
			else
				tempFilePath = Path.GetTempFileName();

			//轉換
			bool deleteTempFile = false;
			try
			{
				//轉換
				TaskState result;
				if (needConversion)
				{
					if (tempFilePath == null)
						encodeTask.Destination = destination;
					else
						encodeTask.Destination = new MediaStorage(tempFilePath);
					result = this.ConvertFormat(version, decodeTask, encodeTask, maxWidth, maxHeight, maintainRatio, preserveMetadata, interpolationMode);
				}
				else
					result = this.CopyToDestination(version, source, destination, tempFilePath);

				//確認轉換結果
				switch (result)
				{
					case TaskState.Executed:
						this.ReportProgress(version, 100);
						break;
					case TaskState.Canceled:
						deleteTempFile = true;
						return true;
					default:
						deleteTempFile = true;
						return false;
				}
			}
			catch
			{
				deleteTempFile = true;
				throw;
			}
			finally
			{
				//刪除暫存檔案
				if (deleteTempFile && tempFilePath != null)
					File.Delete(tempFilePath);
			}

			//儲存結果
			if (tempFilePath != null)
			{
				destination = new MediaStorage(tempFilePath);
				this.WriteValue<MediaStorage>(version, ref _Destination, destination);
				this.WriteValue<bool>(version, ref _IsTempDestinationFile, true);
			}
			
			//完成
			return true;
		}
		#endregion
		#region InterpolationMode
		/// <summary>
		/// 取得或設定縮放影像時所使用的差補模式，預設值為 InterpolationMode.Default。
		/// </summary>
		public Gdi2D.InterpolationMode InterpolationMode
		{
			get
			{
				return _InterpolationMode;
			}
			set
			{
				if (!value.IsDefined())
					throw new ArgumentException();
				this.SetInitializingParameter<Gdi2D.InterpolationMode>(ref _InterpolationMode, value);
			}
		}
		#endregion
		#region IsTempDestinationFile
		/// <summary>
		/// 取得值以確認轉換後的影像是否已儲存至暫存檔案。
		/// </summary>
		public bool IsTempDestinationFile
		{
			get
			{
				return _IsTempDestinationFile;
			}
		}
		#endregion
		#region MaintainAspectRatio
		/// <summary>
		/// 取得或設定縮放影像時是否需維持原始的長寬比例，預設值為 True。
		/// </summary>
		public bool MaintainAspectRatio
		{
			get
			{
				return _MaintainAspectRatio;
			}
			set
			{
				this.SetInitializingParameter<bool>(ref _MaintainAspectRatio, value);
			}
		}
		#endregion
		#region MaxPixelHeight
		/// <summary>
		/// 取得或設定轉換後的最大像素高度，Null (Visual Basic 為 Nothing) 表示不指定像素高度。
		/// </summary>
		public int? MaxPixelHeight
		{
			get
			{
				return _MaxPixelHeight;
			}
			set
			{
				if (value != null && value.Value <= 0)
					throw new ArgumentOutOfRangeException();
				this.SetInitializingParameter<int?>(ref _MaxPixelHeight, value);
			}
		}
		#endregion
		#region MaxPixelWidth
		/// <summary>
		/// 取得或設定轉換後的最大像素寬度，Null (Visual Basic 為 Nothing) 表示不指定像素寬度。
		/// </summary>
		public int? MaxPixelWidth
		{
			get
			{
				return _MaxPixelWidth;
			}
			set
			{
				if (value != null && value.Value <= 0)
					throw new ArgumentOutOfRangeException();
				this.SetInitializingParameter<int?>(ref _MaxPixelWidth, value);
			}
		}
		#endregion
		#region PreserveMetadata
		/// <summary>
		/// 取得或設定轉換時是否保留原始的中繼資料，預設值為 True。
		/// </summary>
		public bool PreserveMetadata
		{
			get
			{
				return _PreserveMetadata;
			}
			set
			{
				this.SetInitializingParameter<bool>(ref _PreserveMetadata, value);
			}
		}
		#endregion
		#region ResetOverride
		/// <summary>
		/// 重設工作狀態。
		/// </summary>
		protected override void ResetOverride()
		{
			//重設狀態
			if (_IsTempDestinationFile)
			{
				_IsTempDestinationFile = false;
				_Destination = null;
			}

			//呼叫基底
			base.ResetOverride();
		}
		#endregion
		#region ResizeImage
		IBitmapImage ResizeImage(IBitmapImage sourceBitmap, int? maxWidth, int? maxHeight, bool maintainRatio, Gdi2D.InterpolationMode interpolationMode)
		{
			//確認參數
			if (maxWidth == null && maxHeight == null)
				return sourceBitmap;
			if (maxWidth <= 0 || maxHeight <= 0)
				return sourceBitmap;

			//計算縮放比例
			double scaleX, scaleY;
			if (maintainRatio)
			{
				if (maxWidth == null)
					maxWidth = int.MaxValue;
				else if (maxHeight == null)
					maxHeight = int.MaxValue;
				scaleX = scaleY = Math.Min(((double)maxWidth.Value / sourceBitmap.PixelWidth), ((double)maxHeight.Value / sourceBitmap.PixelHeight));
			}
			else
			{
				scaleX = (maxWidth != null ? Math.Min(((double)maxWidth.Value / sourceBitmap.PixelWidth), 1) : 1);
				scaleY = (maxHeight != null ? Math.Min(((double)maxHeight.Value / sourceBitmap.PixelHeight), 1) : 1);
			}
			if (scaleX > 1 || scaleY > 1)
				return sourceBitmap;

			//建立新影像
			IBitmapImage targetBitmap;
			if (interpolationMode == Gdi2D.InterpolationMode.Default)
			{
				//取得 BitmapSource
				BitmapSource sourceBitmapSource = sourceBitmap.ToBitmapSource();
				if (sourceBitmapSource == null)
					throw new ArgumentException("Fail to decode source image.");

				//縮小影像
				ScaleTransform transform = new ScaleTransform(scaleX, scaleY);
				BitmapSource targetBitmapSource = new TransformedBitmap(sourceBitmapSource, transform);
				targetBitmapSource.Freeze();
				targetBitmap = new BitmapImage(targetBitmapSource);
			}
			else
			{
				//計算目標大小
				int targetWidth = (int)(sourceBitmap.PixelWidth * scaleX);
				int targetHeight = (int)(sourceBitmap.PixelHeight * scaleY);

				//縮小影像
				using (Gdi.Bitmap sourceGdiBitmap = sourceBitmap.ToGdiBitmap())
				{
					using (Gdi.Bitmap targetGdiBitmap = new Gdi.Bitmap(targetWidth, targetHeight))
					{
						using (Gdi.Graphics graphics = Gdi.Graphics.FromImage(targetGdiBitmap))
						{
							graphics.InterpolationMode = interpolationMode;
							graphics.DrawImage(sourceGdiBitmap, new Gdi.Rectangle(0, 0, targetWidth, targetHeight), new Gdi.Rectangle(0, 0, sourceGdiBitmap.Width, sourceGdiBitmap.Height), Gdi.GraphicsUnit.Pixel);
						}
						targetBitmap = new BitmapImage(targetGdiBitmap);
					}
				}
			}
			return targetBitmap;
		}
		#endregion
		#region Source
		/// <summary>
		/// 取得或設定欲轉換的來源影像儲存。
		/// </summary>
		public MediaStorage Source
		{
			get
			{
				return _Source;
			}
			set
			{
				this.SetInitializingParameter<MediaStorage>(ref _Source, value);
			}
		}
		#endregion
		#region ValidateInitParameters
		/// <summary>
		/// 驗證初始化參數是否正確。
		/// </summary>
		/// <returns>True 表示所有初始化參數皆正確，False 表示不完全正確。</returns>
		protected override bool ValidateInitParameters()
		{
			//呼叫基底
			if (!base.ValidateInitParameters())
				return false;

			//確認參數
			if (_Source == null)
				return false;

			//完成
			return true;
		}
		#endregion
	}
}
